#include "debridADTorrent.h"

debridADTorrent::debridADTorrent(CURL *copyHandle, CURLSH *cookieShare, debridFetcherAD *newParent) : debridDownloader(copyHandle) {
    firstRun = true;
    mustExit = false;
    threadRunning = false;
    if (newParent != NULL)
        parent = newParent;
    //FIXME dirty hack cause it's late
    //Share the cookies with the parent AD fetcher (login credentials)
    curl_easy_setopt(curlHandle, CURLOPT_SHARE, cookieShare);
    pthread_mutex_init(&dataLock, NULL);
    //Start the thread one time to be sure no torrent is waiting for us
    start();
}

debridADTorrent::~debridADTorrent() {
    if (threadRunning)
        stop(true);
    pthread_mutex_destroy(&dataLock);
}

void debridADTorrent::attachParent(debridFetcherAD *newParent) {
    if (newParent != NULL)
        parent = newParent;
}

bool debridADTorrent::addTorrent(const std::string& path) {
    if (path.empty())
        return false;
    //File extension is wrong, we won't take the risk
    if (path.compare(path.length()-8, 8, ".torrent"))
        return false;
    std::ifstream torrentFile(path.c_str(), std::ios::binary);
    //Check if the file exists
    if (!torrentFile.is_open())
        return false;
    torrentFile.seekg(0, std::ios_base::end);
    std::streampos fileSize = torrentFile.tellg();
    //If bigger than 5MB
    if (fileSize > TORRENT_MAX_SIZE)
        return false;
    torrentFile.seekg(0, std::ios_base::beg);
    //Check the internals to see if it's a real torrent file or crap
    char buffer[TORRENT_SEEK_SIZE];
    torrentFile.read(buffer, TORRENT_SEEK_SIZE-1);
    buffer[torrentFile.gcount()] = '\0';
    unsigned short torrentScore = 0;
    if (strstr(buffer, "8:announce"))           //A torrent file should have at least one announce so we only check for announce
        ++torrentScore;
    if (strstr(buffer, "13:announce-list"))     //List of other announce
        ++torrentScore;
    if (strstr(buffer, "6:lengthi"))            //Length of the torrent (integer value)
        ++torrentScore;
    if (strstr(buffer, "4:name"))               //Name of the torrent
        ++torrentScore;
    if (strstr(buffer, "7:comment"))            //Torrent comment
        ++torrentScore;
    if (strstr(buffer, "13:comment.utf-8"))     //Torrent comment (UTF-8)
        ++torrentScore;
    if (strstr(buffer, "10:created by"))        //Creator
        ++torrentScore;
    if (strstr(buffer, "13:creation datei"))    //Date of creation
        ++torrentScore;
    if (strstr(buffer, "8:encoding"))           //Encoding type
        ++torrentScore;
    if (strstr(buffer, "12:piece lengthi"))     //Size of a piece, might be after a NULL caracter
        ++torrentScore;
    if (strstr(buffer, "6:pieces"))             //Number of a pieces, same as above
        ++torrentScore;
    if (torrentScore >= TORRENT_MIN_SCORE) {
        appendTorrent(path);                    //Now we're pretty sure we've got a torrent
        return true;
    }
    return false;
}

bool debridADTorrent::addMagnet(const std::string& magnet) {
    if (magnet.compare(0, 7, "magnet:"))
        return false;
    appendTorrent(magnet);
    return true;
}

void debridADTorrent::appendTorrent(const std::string& torrentLink) {
    pthread_mutex_lock(&dataLock);
    torrentList.push_back(torrentLink);
    pthread_mutex_unlock(&dataLock);
    if (!threadRunning) {
        if (start())
            parent->logMessage(_("Starting torrent processor."), MISC);
    }
    std::string msg = _("Added new torrent: ");
    msg.append(torrentLink);
    parent->logMessage(msg, MISC);
}

void debridADTorrent::processList() {
    std::string curLink;
    pthread_mutex_lock(&dataLock);
    parent->logMessage(_("Processing torrent list."), MISC);
    while (!torrentList.empty()) {
        curLink = torrentList.front();
        pthread_mutex_unlock(&dataLock);
        parent->logMessage(_("Processing torrent link."), MISC);
        processTorrent(curLink);
        //Not a magnet we need to delete it from here.
        if (curLink.compare(0, 7, "magnet:")) {
            if (remove(curLink.c_str()) != 0) {
                parent->logMessage(_("Unable to remove torrent file, it'll stay in the working directory."), MISC_ERR);
            }
        }
        while (pollTorrentStatus()) {
            //Same value as AllDebrid (10*300) used to reduce hang at exit
            for (int i=0; i < 10; ++i) {
                if (mustExit)
                    return;
                pSleep(300);
            }
        }
        pthread_mutex_lock(&dataLock);
        torrentList.pop_front();
    }
    parent->logMessage(_("Finished processing torrent list."), MISC);
    parent->logMessage(_("Stopping torrent processor."), MISC);
    pthread_mutex_unlock(&dataLock);
}

bool debridADTorrent::processTorrent(const std::string& torrent) {
    struct curl_httppost *formPost=NULL;
    struct curl_httppost *formPostEnd=NULL;
    bool retStatus = false;
    if (!torrent.compare(0, 7, "magnet:")) {
        curl_formadd(&formPost, &formPostEnd, CURLFORM_COPYNAME, "magnet", CURLFORM_COPYCONTENTS, torrent.c_str(), CURLFORM_END);
        curl_formadd(&formPost, &formPostEnd, CURLFORM_COPYNAME, "uploadedfile", CURLFORM_COPYCONTENTS, "", CURLFORM_CONTENTTYPE, "application/x-bittorrent", CURLFORM_END);
    }
    else {
        curl_formadd(&formPost, &formPostEnd, CURLFORM_COPYNAME, "magnet", CURLFORM_COPYCONTENTS, "", CURLFORM_END);
        curl_formadd(&formPost, &formPostEnd, CURLFORM_COPYNAME, "uploadedfile", CURLFORM_FILE, torrent.c_str(), CURLFORM_CONTENTTYPE, "application/x-bittorrent", CURLFORM_END);
    }
    curl_formadd(&formPost, &formPostEnd, CURLFORM_COPYNAME, "submit", CURLFORM_COPYCONTENTS, "Convert this torrent", CURLFORM_END);
    std::string retPage = postMultiPart("http://www.alldebrid.com/torrent/", formPost);
    //We could be using the error message returned in the page, but if it fails it's 200 OK and if it succeed it's 302 Moved
    /*if (retPage.find("An error is append :") != std::string::npos)
        retStatus = false;*/
    long responseCode;
    if (curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &responseCode) == CURLE_OK) {
        if (responseCode == 302) {   //Redirect
            retStatus = true;
        }
    }
    //Reset cURL to prevent it from searching formPost data.
    loadCURLSettings();
    //Shouldn't free it till curl isn't freeed! Shitstorm coming !
    curl_formfree(formPost);
    return retStatus;
}

void debridADTorrent::setExit(bool newState) {
    mustExit = newState;
    //If we must exit the loop then we put the flag for the next run
    if (firstRun);
        firstRun = false;
}

bool debridADTorrent::getExit() {
    return mustExit;
}

void debridADTorrent::setRunning(bool newState) {
    threadRunning = newState;
}

bool debridADTorrent::isFirstRun() {
    return firstRun;
}

bool debridADTorrent::pollTorrentStatus(bool isResuming) {
    //Should be http://www.alldebrid.com/api/torrent.php?json=true&randval=0.[rand16]&_=[timestamp-ms]
    std::string retPage = getData("http://www.alldebrid.com/api/torrent.php?json=true");
    if (retPage.empty())
        return false;
    size_t findPos = retPage.find("\",\"");
    size_t endPos;
    std::string torrentState;
    if (findPos != std::string::npos) {
        findPos += 3;
        //We search for ", and not "," because sometimes the field is NULL = ",0,"
        endPos = retPage.find("\",", findPos);
        if (endPos != std::string::npos) {
            torrentState = retPage.substr(findPos, endPos-findPos);
            /*if (!torrentState.compare(7, 11, "Downloading")) {
                return true;
            }
            else if (!torrentState.compare(0, 7, "Pending")) {
                return true;
            }*/
            //Yes, developpers are using '' around this value
            //else if (!torrentState.compare(0, 10, "'finished'")) {
            if (!torrentState.compare(0, 10, "'finished'")) {
                findPos = retPage.find("Download list below :<br \\/>", endPos);
                if (findPos != std::string::npos) {
                    findPos += 28;
                    //endPos = retPage.find("<br \\/>", findPos);
                    endPos = retPage.find("\\\"", findPos);
                    if (endPos != std::string::npos) {
                        std::string torrentLink = retPage.substr(findPos, endPos-findPos);
                        //Remove escaping caracters
                        size_t erasePos;
                        while ((erasePos = torrentLink.find('\\')) != std::string::npos)
                            torrentLink.erase(erasePos, 1);
                        //Replace every <br /> with a newline
                        while ((erasePos = torrentLink.find("<br />")) != std::string::npos) {
                            torrentLink.replace(erasePos, 2, "\r\n");
                            torrentLink.erase(erasePos+2, 4);
                        }
                        //Now that we've got the link we need to delete the download from interface
                        findPos = retPage.find("a>\",\"<a href='", endPos);
                        if (findPos != std::string::npos) {
                            findPos += 43;
                            endPos = retPage.find("'>remove<", findPos);
                            if (endPos != std::string::npos) {
                                //Sometimes something goes wrong and the download will fail even if it says someting else
                                if (!torrentLink.empty())
                                    parent->addLink(torrentLink);
                                std::string request = "http://www.alldebrid.com/torrent/?action=remove&id=";
                                request.append(retPage.substr(findPos, endPos-findPos));
                                getData(request);
                                if (isResuming)
                                    return true;
                            }
                        }
                    }
                }
            }
            else
                return true;
        }
    }
    return false;
}

void *debridADTorrent::threadEntry(void *data) {
    debridADTorrent *objPtr = (debridADTorrent*)data;
    //Check that any torrent isn't in an inconsistent state
    while (objPtr->pollTorrentStatus(true) && !objPtr->getExit()) {
        //Same value as AllDebrid (10*300) used to reduce hang at exit
        for (int i=0; i < 10; ++i) {
            if (objPtr->getExit())
                break;
            pSleep(300);
        }
    }
    if (!objPtr->isFirstRun())
        objPtr->processList();
    objPtr->setExit(false);
    objPtr->setRunning(false);
    return 0;
}

bool debridADTorrent::createThread() {
    if (!pthread_create(&thread, NULL, threadEntry, (void*)this))
        return true;
    return false;
}

bool debridADTorrent::deleteThread() {
    if (!pthread_join(thread, NULL))
        return true;
    return false;
}

bool debridADTorrent::start() {
    if (createThread()) {
        threadRunning = true;
        return true;
    }
    return false;
}

bool debridADTorrent::cancel() {
    if (threadRunning) {
        mustExit = true;
        return true;
    }
    return false;
}

void debridADTorrent::stop(bool forceExit) {
    if (forceExit)
        cancel();
    deleteThread();
}
